package dev.ece.suit.ui;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;

import javax.imageio.ImageIO;

import dev.ece.core.cells.model.AbstractUI;
import dev.ece.core.cells.model.UI;
import dev.ece.core.cells.model.base.SquareOfNineUI;
import dev.ece.core.cells.model.base.SquareOfThreeUI;
import dev.ece.core.cells.model.base.SquareUI;
import dev.ece.core.cells.world.World2D;
import dev.ece.core.shader.DefaultTextureShader;
import dev.ece.core.texture.Texture;
import dev.ece.suit.ui.event.ClickListener;
import dev.ece.suit.ui.event.MouseListener;
import dev.ece.util.lib.IntOffset;
import dev.ece.util.lib.IntRectangle;
import dev.ece.util.lib.Rectangle;
import dev.ece.util.lib.Vector2f;
import dev.ece.util.obj.ObjectCount;
import dev.ece.util.tools.Camera;
import dev.ece.util.tools.Constants;
import dev.ece.util.tools.ploy.TextureEdgePloy;

public class Component extends dev.ece.core.sprite.Component<Float, AbstractUI<Float>> {

	/**
	 * 
	 */
	private static final long serialVersionUID = 8401527915394636009L;
	
	private static final Map<String, ObjectCount<Texture>> IMAGES = new LinkedHashMap<String, ObjectCount<Texture>>();
	
	public final static Observer DEFAULT_DESTROYER = new Observer() {

		@Override
		@SuppressWarnings("unchecked")
		public void update(Observable o, Object arg) {
			ObjectCount<Texture> count = (ObjectCount<Texture>) o;
			if(count.needDestroy()) {
				count.getObject().destroy();
			}
		}
		
	};
	
	public final static Observer IMAGE_DESTROYER = new Observer() {

		@Override
		@SuppressWarnings("unchecked")
		public void update(Observable o, Object arg) {
			ObjectCount<Texture> count = (ObjectCount<Texture>) o;
			if(count.needDestroy()) {
				IMAGES.remove(count.getName());
				count.getObject().destroy();
			}
		}
		
	};
	
	private DefaultTextureShader defaultTextureShader;
	
	private ObjectCount<Texture> background;
	
	private Collection<ClickListener> clickListeners = Collections.synchronizedCollection(new ArrayList<ClickListener>());
	
	private Collection<MouseListener> mouseListeners = Collections.synchronizedCollection(new ArrayList<MouseListener>());
	
	private IntRectangle padding = new IntRectangle();
	
	public Component(String background, float width, float height) throws IOException {
		this(new SquareUI<Float>(width, height), background);
	}
	
	public Component(String background, float width, float height, IntRectangle centerClipping) throws IOException {
		this(getImageTexture(background), width, height, centerClipping);
	}
	
	public Component(String background, float width, float height, IntOffset centerClipping) throws IOException {
		this(getImageTexture(background), width, height, centerClipping);
	}
	
	public Component(String background, IntRectangle textureClipping, float width, float height) throws IOException {
		this(getImageTexture(background), textureClipping, width, height);
	}
	
	public Component(String background, IntRectangle textureClipping, float width, float height, IntRectangle centerClipping) throws IOException {
		this(getImageTexture(background), textureClipping, width, height, centerClipping);
	}
	
	public Component(String background, IntRectangle textureClipping, float width, float height, IntOffset centerClipping) throws IOException {
		this(getImageTexture(background), textureClipping, width, height, centerClipping);
	}
	
	/**
	 * 为了提高纹理重用性,不推荐用此构造方法
	 * @param image
	 * @param width
	 * @param height
	 */
	public Component(BufferedImage image, float width, float height) {
		this(new SquareUI<Float>(width, height), image);
	}
	
	/**
	 * 为了提高纹理重用性,不推荐用此构造方法
	 * @param image
	 * @param width
	 * @param height
	 * @param centerClipping
	 */
	public Component(BufferedImage image, float width, float height, IntRectangle centerClipping) {
		this(new SquareOfNineUI<Float>(width, height, centerClipping) {

			@Override
			protected Vector2f createTextureSize() {
				return new Vector2f(image.getWidth(), image.getHeight());
			}
			
		}, image);
	}
	
	/**
	 * 为了提高纹理重用性,不推荐用此构造方法
	 * @param image
	 * @param width
	 * @param height
	 * @param centerClipping
	 */
	public Component(BufferedImage image, float width, float height, IntOffset centerClipping) {
		this(new SquareOfThreeUI<Float>(width, height, centerClipping) {

			@Override
			protected Vector2f createTextureSize() {
				return new Vector2f(image.getWidth(), image.getHeight());
			}
			
		}, image);
	}
	
	/**
	 * 为了提高纹理重用性,不推荐用此构造方法
	 * @param image
	 * @param textureClipping
	 * @param width
	 * @param height
	 */
	public Component(BufferedImage image, IntRectangle textureClipping, float width, float height) {
		this(new SquareUI<Float>(width, height, 
				textureClipping.toTextureSizeRectangle(image.getWidth(), image.getHeight())), image);
	}
	
	/**
	 * 为了提高纹理重用性,不推荐用此构造方法
	 * @param image
	 * @param textureClipping
	 * @param width
	 * @param height
	 * @param centerClipping
	 */
	public Component(BufferedImage image, IntRectangle textureClipping, float width, float height, IntRectangle centerClipping) {
		this(new SquareOfNineUI<Float>(textureClipping.toTextureSizeRectangle(image.getWidth(), image.getHeight()), width, height, centerClipping) {

			@Override
			protected Vector2f createTextureSize() {
				return new Vector2f(image.getWidth(), image.getHeight());
			}
			
		}, image);
	}
	
	/**
	 * 为了提高纹理重用性,不推荐用此构造方法
	 * @param image
	 * @param textureClipping
	 * @param width
	 * @param height
	 * @param centerClipping
	 */
	public Component(BufferedImage image, IntRectangle textureClipping, float width, float height, IntOffset centerClipping) {
		this(new SquareOfThreeUI<Float>(textureClipping.toTextureSizeRectangle(image.getWidth(), image.getHeight()), width, height, centerClipping) {

			@Override
			protected Vector2f createTextureSize() {
				return new Vector2f(image.getWidth(), image.getHeight());
			}
			
		}, image);
	}
	
	/**
	 * 
	 * @param background
	 * @param width
	 * @param height
	 */
	public Component(ObjectCount<Texture> background, float width, float height) {
		this(new SquareUI<Float>(width, height), background);
	}
	
	/**
	 * 
	 * @param background
	 * @param width
	 * @param height
	 * @param centerClipping
	 */
	public Component(ObjectCount<Texture> background, float width, float height, IntRectangle centerClipping) {
		this(new SquareOfNineUI<Float>(width, height, centerClipping) {

			@Override
			protected Vector2f createTextureSize() {
				return new Vector2f(background.getObject().getWidth(), background.getObject().getHeight());
			}
			
		}, background);
	}
	
	/**
	 * 
	 * @param background
	 * @param width
	 * @param height
	 * @param centerClipping
	 */
	public Component(ObjectCount<Texture> background, float width, float height, IntOffset centerClipping) {
		this(new SquareOfThreeUI<Float>(width, height, centerClipping) {

			@Override
			protected Vector2f createTextureSize() {
				return new Vector2f(background.getObject().getWidth(), background.getObject().getHeight());
			}
			
		}, background);
	}
	
	/**
	 * 
	 * @param background
	 * @param textureClipping
	 * @param width
	 * @param height
	 */
	public Component(ObjectCount<Texture> background, IntRectangle textureClipping, float width, float height) {
		this(new SquareUI<Float>(width, height, 
				textureClipping.toTextureSizeRectangle(
						background.getObject().getWidth(), background.getObject().getHeight())), background);
	}
	
	/**
	 * 
	 * @param background
	 * @param textureClipping
	 * @param width
	 * @param height
	 * @param centerClipping
	 */
	public Component(ObjectCount<Texture> background, IntRectangle textureClipping, float width, float height, IntRectangle centerClipping) {
		this(new SquareOfNineUI<Float>(textureClipping.toTextureSizeRectangle(background.getObject().getWidth(), background.getObject().getHeight()), width, height, centerClipping) {

			@Override
			protected Vector2f createTextureSize() {
				return new Vector2f(background.getObject().getWidth(), background.getObject().getHeight());
			}
			
		}, background);
	}
	
	/**
	 * 
	 * @param background
	 * @param textureClipping
	 * @param width
	 * @param height
	 * @param centerClipping
	 */
	public Component(ObjectCount<Texture> background, IntRectangle textureClipping, float width, float height, IntOffset centerClipping) {
		this(new SquareOfThreeUI<Float>(textureClipping.toTextureSizeRectangle(background.getObject().getWidth(), background.getObject().getHeight()), width, height, centerClipping) {

			@Override
			protected Vector2f createTextureSize() {
				return new Vector2f(background.getObject().getWidth(), background.getObject().getHeight());
			}
			
		}, background);
	}
	
	public Component(AbstractUI<Float> ui, String background) throws IOException {
		super(ui);
		this.defaultTextureShader = this.createDefaultTextureShader();
		this.background = this.createBackground(background);
		this.setup();
	}
	
	public Component(AbstractUI<Float> ui, BufferedImage background) {
		super(ui);
		this.defaultTextureShader = this.createDefaultTextureShader();
		this.background = this.createBackground(background);
		this.setup();
	}
	
	public Component(AbstractUI<Float> ui, ObjectCount<Texture> background) {
		super(ui);
		this.defaultTextureShader = this.createDefaultTextureShader();
		this.background = background;
		this.setup();
	}
	
	protected void setup() {}
	
	public static ObjectCount<Texture> getImageTexture(String filepath) throws IOException {
		File file = Constants.getGameFile(filepath);
		String filename = file.getAbsolutePath();
		ObjectCount<Texture> texture = IMAGES.get(filename);
		if(texture == null) {
			BufferedImage image = ImageIO.read(file);
			texture = new ObjectCount<Texture>(filename, Texture.newInstance(image, TextureEdgePloy.getInstance()));
			texture.addObserver(IMAGE_DESTROYER);
			IMAGES.put(filename, texture);
		}
		texture.addCount();
		return texture;
	}

	protected DefaultTextureShader createDefaultTextureShader() {
		return DefaultTextureShader.getDefaultTextureShader();
	}
	
	protected ObjectCount<Texture> createBackground(BufferedImage image) {
		ObjectCount<Texture> result = new ObjectCount<Texture>("", Texture.newInstance(image, TextureEdgePloy.getInstance()));
		result.addObserver(DEFAULT_DESTROYER);
		result.addCount();
		return result;
	}
	
	protected ObjectCount<Texture> createBackground(String background) throws IOException {
		return getImageTexture(background);
	}
	
	protected void glUseShader() {
		getDefaultTextureShader().glUseProgram();
		getDefaultTextureShader().glModelViewProjectMatrix();
		getDefaultTextureShader().glTexture(getBackground());
	}
	
	@Override
	public void onRender(Camera camera) {
		this.glUseShader();
		super.onRender(camera);
	}

	protected void glPushScissor() {
		World2D world = World2D.currentWorld2D();
		//截取面板中信息
		Rectangle rect = this.getAbsoluteViewRectangle();
		float x = rect.getLeft() + world.getScreen().getWidth() / 2;
		float y = rect.getBottom() + world.getScreen().getHeight() / 2;
		this.glPushScissor(
				(int) x, 
				(int) y, 
				(int) (rect.right - rect.left), 
				(int) (rect.top - rect.bottom));
	}
	
	public Rectangle getAbsoluteViewRectangle() {
		Rectangle rect = this.getViewRectangle();
		Vector2f position = this.getAbsolutePosition();
		rect.left += position.getX();
		rect.right += position.getX();
		rect.top += position.getY();
		rect.bottom += position.getY();
		return rect;
	}
	
	public Rectangle getViewRectangle() {
		Rectangle rect = this.getRectangle();
		IntRectangle padding = getPadding();
		rect.left += padding.left;
		rect.top -= padding.top;
		rect.right -= padding.right;
		rect.bottom += padding.bottom;
		return rect;
	}
	
	public void click() {
		for(ClickListener listener: clickListeners) {
			listener.onClicked(this);
		}
	}
	
	@Override
	public boolean onMouseButtonClick(int button, float x, float y) {
		this.click();
		for(MouseListener mouseListener: mouseListeners) {
			mouseListener.onMouseButtonClick(button, x, y);
		}
		return true;
	}

	@Override
	public boolean onMouseMove(float x, float y) {
		for(MouseListener mouseListener: mouseListeners) {
			mouseListener.onMouseMove(x, y);
		}
		return true;
	}

	@Override
	public boolean onMouseButtonDown(int button, float x, float y) {
		for(MouseListener mouseListener: mouseListeners) {
			mouseListener.onMouseButtonDown(button, x, y);
		}
		return true;
	}

	@Override
	public boolean onMouseButtonUp(int button, float x, float y) {
		for(MouseListener mouseListener: mouseListeners) {
			mouseListener.onMouseButtonUp(button, x, y);
		}
		return false;
	}

	@Override
	public boolean onMouseScroll(double xoffset, double yoffset, float x, float y) {
		for(MouseListener mouseListener: mouseListeners) {
			mouseListener.onMouseScroll(xoffset, yoffset, x, y);
		}
		return true;
	}

	protected Collection<ClickListener> getClickListeners() {
		return clickListeners;
	}

	protected Collection<MouseListener> getMouseListeners() {
		return mouseListeners;
	}

	public void addClickListener(ClickListener listener) {
		if(!containsClickListener(listener)) {
			getClickListeners().add(listener);
		}
	}
	
	public boolean containsClickListener(ClickListener listener) {
		return getClickListeners().add(listener);
	}
	
	public void removeClickListener(ClickListener listener) {
		getClickListeners().remove(listener);
	}
	
	public int clickListenersLength() {
		return getClickListeners().size();
	}
	
	public void addMouseListener(MouseListener mouseListener) {
		if(!containsMouseListener(mouseListener)) {
			getMouseListeners().add(mouseListener);
		}
	}
	
	public boolean containsMouseListener(MouseListener listener) {
		return getMouseListeners().add(listener);
	}
	
	public void removeMouseListener(MouseListener mouseListener) {
		getMouseListeners().remove(mouseListener);
	}
	
	public int mouseListenersLength() {
		return getMouseListeners().size();
	}
	
	public DefaultTextureShader getDefaultTextureShader() {
		return defaultTextureShader;
	}

	public ObjectCount<Texture> getBackgroundObjectCountTexture() {
		return background;
	}
	
	@Override
	protected void setParent(dev.ece.core.sprite.Component<?, ? extends UI<?>> parent) {
		super.setParent(parent);
	}

	public Rectangle getRectangleFromParent() {
		Rectangle rect = getRectangle();
		rect.left += position.getX();
		rect.right += position.getX();
		rect.top += position.getY();
		rect.bottom += position.getY();
		return rect;
	}
	
	public Rectangle getAbsoluteRectangle() {
		Rectangle rect = getRectangle();
		Vector2f position = getAbsolutePosition();
		rect.left += position.getX();
		rect.right += position.getX();
		rect.top += position.getY();
		rect.bottom += position.getY();
		return rect;
	}
	
	public Rectangle getRectangle() {
		float hw = this.getWidth() / 2;
		float hh = this.getHeight() / 2;
		return new Rectangle(-hw, hh, hw, -hh);
	}
	
	public Texture getBackground() {
		return getBackgroundObjectCountTexture().getObject();
	}

	@Override
	public float getWidth() {
		return this.getUi().getWidth();
	}

	@Override
	public float getHeight() {
		return this.getUi().getHeight();
	}

	@Override
	public void destroy() {
		super.destroy();
		if(this.background != null) {
			this.background.deleteCount();
		}
	}

	@Override
	public void clearup() {
		//清除点击事件
		this.clickListeners.clear();
	}

	public IntRectangle getPadding() {
		return padding;
	}

	public void setPadding(IntRectangle padding) {
		if(padding == null) {
			throw new IllegalArgumentException("padding不能为null");
		}
		this.padding = padding;
	}
	
}
